home *** CD-ROM | disk | FTP | other *** search
Text File | 1995-05-01 | 38.9 KB | 1,463 lines | [TEXT/MMCC] |
- /*----------------------------------------------------------------------------
-
- nntp.c
-
- This reentrant and reusable module implements an interface to NNTP
- (USENET news) servers.
-
- The following mandatory functions handle initialization and idle time
- tasks:
-
- NntpInit - Initialize the module.
- NntpIdle - Perform idle time tasks.
-
- The following functions work with NNTP streams:
-
- NntpOpen - Open an NNTP stream.
- NntpClose - Close an NNTP stream.
- NntpAbort - Abort an NNTP stream.
- NntpSetStreamOptions - Set stream options.
- NntpGetGroupNames - Get group names.
- NntpGetGroupInfo - Get info for a single group.
- NntpGetMultipleGroupInfo - Get info for multiple groups.
- NntpGetHeaders - Get selected article headers.
- NntpGetArticle - Get an article.
- NntpPostArticle - Post an article.
- NntpAuthorize - Authorize user.
- NntpGetHello - Get server hello message.
- NntpGetHelp - Get server help text.
- NntpGetIPAddr - Get server IP address.
- NntpGetServerErrInfo - Get server error information.
-
- You must call memutil.c/InitMemUtil, net.c/NetInit, and nntp.c/NntpInit
- before calling any of the other functions in this module. You also must call
- both the NntpIdle and the NetIdle function in your idle loop, and the NetTerm
- function at program termination.
-
- A "stream" is an abstraction representing a bidirectional network connection
- to an NNTP server. A stream is represented as a variable of type "NntpStreamRef".
- These stream references are opaque. You may copy them and pass them as parameters
- to functions in nntp.c, but you are prohibited from accessing the contents of
- the memory blocks pointed to by the references. Only the functions in
- nntp.c are permitted to manipulate the contents of these blocks.
-
- The functions return a value of type OSErr as the function result:
-
- noErr no error occurred
- nntpServerErr server error
- nntpNoSuchGroupErr no such group or group access restriction
- nntpNoSuchArticleErr no such article
- nntpAuthFailedErr authorization failed
- other any other OS or Toolbox error code
-
- If the function result is nntpServerErr, the NntpGetServerErrInfo function can
- be called to get information about the server error.
-
- On server errors, no such group errors, and no such article errors, the stream
- is still open and allocated on return to the caller and may be reused.
-
- If an OS or Toolbox error occurs, or if authorization fails, the connection to
- the news server is aborted, but the NNTP stream is left allocated. The module
- will automatically attempt to reestablish a new connection the next time the
- stream is used. Note that this is a major difference between nntp.c and the ftp.c
- and smtp.c modules.
-
- NntpOpen is an exception to these rules. For this function, if the return value is
- noErr, nntpServerErr, or nntpAuthFailedErr, then the stream is allocated and can
- be reused. If the return value is an OS or Toolbox error code, however, then the
- stream is not allocated, and cannot be reused.
-
- All group names, header names, message id, and pattern strings passed as parameters
- to functions in this module are restricted to a maximum of 127 characters. If they
- exceed this maximum, they are truncated.
-
- The following options are associated with each stream:
-
- idleTime stream is automatically closed if idle for this
- many minutes, or 0 for no automatic close
- useXPAT true to use XPAT command for searches
- sendModeReader true to send MODE READER command after connect
- batchedCmds true to use batched GROUP commands
- newConnection true to establish new connection before getting
- group info
- authOnConnect true to authorize on each connection
- username authorization username
- password authorization password
-
- The default values are idleTime=0, useXPAT=false, sendModeReader=true,
- batchedCmds=false, newConnection=true, and authOnConnect=false.
- See the individual function descriptions more for more details.
-
- Copyright © 1994-1995, Northwestern University.
-
- ----------------------------------------------------------------------------*/
-
- #include <stdlib.h>
- #include <string.h>
- #include <stdio.h>
- #include <ctype.h>
-
- #include "def.h"
- #include "nntp.h"
- #include "net.h"
- #include "strutil.h"
- #include "qsort.h"
- #include "memutil.h"
-
-
-
- /* Types. */
-
- typedef struct TStream {
- NetStreamRef netStream; /* net stream reference, or nil if closed */
- NntpStreamOptions options; /* stream options */
- Boolean serverHasXPAT; /* true if server supports XPAT command */
- unsigned long addr; /* server IP address */
- unsigned short port; /* server port number */
- CStr255 helloMsg; /* hello message */
- char curGroup[128]; /* current group on server */
- unsigned long lastTransactionTime; /* time of last transaction on stream */
- struct TStream **next; /* handle to next open NNTP stream */
- } TStream, *TStreamPtr, **TStreamHandle;
-
- typedef struct TGetMultipleGroupInfo {
- NntpGroupInfoHandle groupInfo; /* handle to group info array */
- long index; /* current index in group info array */
- Boolean serverError; /* true if server error encountered */
- } TGetMultipleGroupInfo;
-
-
-
- /* Global variables. */
-
- static TStreamHandle gStreamList = nil; /* handle to list of open NNTP streams */
- static NntpGiveTimeFunction gGiveTime; /* GiveTime function */
-
-
-
- /*----------------------------------------------------------------------------
- SetLastTransactionTime
-
- Record the transaction time on an NNTP stream.
-
- Entry: s = stream reference.
- ----------------------------------------------------------------------------*/
-
- static void SetLastTransactionTime (TStreamHandle s)
- {
- unsigned long lastTransactionTime;
-
- GetDateTime(&lastTransactionTime);
- (**s).lastTransactionTime = lastTransactionTime;
- }
-
-
-
- /*----------------------------------------------------------------------------
- SetCurrentGroup
-
- Record the current group on an NNTP stream.
-
- Entry: s = stream reference.
- group = group name.
- ----------------------------------------------------------------------------*/
-
- static void SetCurrentGroup (TStreamHandle s, char *group)
- {
- short len;
-
- len = strlen(group);
- if (len > 127) len = 127;
- BlockMoveData(group, (**s).curGroup, len);
- *((**s).curGroup + len) = 0;
- }
-
-
-
- /*----------------------------------------------------------------------------
- Authorize
-
- Authorize user.
-
- Entry: s = stream reference.
-
- Exit: function result = result code.
-
- If authorization fails the function returns nntpAuthFailedErr.
- In this case, the net stream is closed.
- ----------------------------------------------------------------------------*/
-
- static OSErr Authorize (TStreamHandle s)
- {
- NetStreamRef netStream;
- char username[32], password[32];
- CStr255 command, response;
- long responseCode;
- OSErr err = noErr;
-
- strcpy(username, (**s).options.username);
- strcpy(password, (**s).options.password);
-
- if (*username == 0 || *password == 0) return noErr;
-
- netStream = (**s).netStream;
-
- sprintf(command, "AUTHINFO USER %s", username);
- err = NetCommand(netStream, command, &responseCode, response);
- if (err != noErr) goto exit1;
- if (responseCode < 300 || responseCode > 399) goto exit2;
-
- sprintf(command, "AUTHINFO PASS %s", password);
- err = NetCommand(netStream, command, &responseCode, response);
- if (err != noErr) goto exit1;
- if (responseCode < 200 || responseCode > 299) goto exit2;
-
- return noErr;
-
- exit1:
-
- (**s).netStream = nil;
- return err;
-
- exit2:
-
- NetClose(netStream);
- (**s).netStream = nil;
- return nntpAuthFailedErr;
- }
-
-
-
- /*----------------------------------------------------------------------------
- ReOpenConnection
-
- Reopen a connection to an NNTP server if necessary.
-
- Entry: s = stream reference.
-
- Exit: function result = result code.
- ----------------------------------------------------------------------------*/
-
- static OSErr ReOpenConnection (TStreamHandle s)
- {
- NetStreamRef netStream;
- CStr255 command, response;
- long responseCode;
- OSErr err = noErr;
-
- if ((**s).netStream != nil) return noErr;
-
- NetIdle();
-
- err = NetOpen((**s).addr, (**s).port, true, &netStream, &responseCode, response);
- if (err != noErr) return err;
-
- (**s).netStream = netStream;
- *(**s).curGroup = 0;
- strcpy((**s).helloMsg, response);
-
- if (responseCode != 200 && responseCode != 201) return nntpServerErr;
-
- if ((**s).options.sendModeReader) {
- strcpy(command, "MODE READER");
- err = NetCommand(netStream, command, &responseCode, response);
- if (err != noErr) goto exit;
- }
-
- if ((**s).options.authOnConnect) {
- err = Authorize(s);
- if (err != noErr) return err;
- }
-
- SetLastTransactionTime(s);
-
- return noErr;
-
- exit:
-
- (**s).netStream = nil;
- return err;
- }
-
-
- /*----------------------------------------------------------------------------
- DoOneGroupCmdResponse
-
- Process one GROUP command response for batched GROUP commands.
-
- Entry: responseCode = resonse code.
- response = response string.
- userDataPtr = pointer to TGetMultipleGroupInfo struct.
-
- This function is used by NntpGetMultipleGroupInfo.
- ----------------------------------------------------------------------------*/
-
- static void DoOneGroupCmdResponse (long responseCode, CStr255 response,
- Ptr userDataPtr)
- {
- TGetMultipleGroupInfo *z;
- NntpGroupInfoPtr x;
- char *p;
- char state;
- NntpGroupInfoHandle groupInfo;
- long index;
-
- z = (TGetMultipleGroupInfo*)userDataPtr;
- if (z->serverError) return;
- groupInfo = z->groupInfo;
- index = z->index;
- state = MyHGetState(groupInfo);
- MyHLock(groupInfo);
- x = *groupInfo + index;
- if (responseCode == 211) {
- p = response;
- CrackNum(&p);
- x->count = CrackNum(&p);
- x->first = CrackNum(&p);
- x->last = CrackNum(&p);
- x->ok = true;
- } else if (responseCode == 502) {
- x->count = 0;
- x->first = 1;
- x->last = 0;
- x->ok = true;
- } else if (responseCode == 411) {
- x->ok = false;
- } else {
- z->serverError = true;
- }
- MyHSetState(groupInfo, state);
- z->index = index + 1;
- }
-
-
-
- /*----------------------------------------------------------------------------
- SortHeadersCompare
-
- This comparison function is used to sort a header info array into increasing
- order by article number.
-
- Entry: p = pointer to first NntpHeaderInfo struct.
- q = pointer to second NntpHeaderInfo struct.
-
- Exit: function result = error code.
- *result =
- -1 if p->number < q->number
- 0 if p->number == q->number
- 1 if p->number > q->number
- ----------------------------------------------------------------------------*/
-
- static OSErr SortHeadersCompare (NntpHeaderInfoPtr p, NntpHeaderInfoPtr q,
- short *result)
- {
- OSErr err;
- static short counter = 0;
-
- if ((++counter & 0x1f) == 0) {
- err = (*gGiveTime)();
- if (err != noErr) return err;
- counter = 0;
- }
-
- if (p->number < q->number) {
- *result = -1;
- } else if (p->number == q->number) {
- *result = 0;
- } else {
- *result = 1;
- }
- return noErr;
- }
-
-
-
- /*----------------------------------------------------------------------------
- NntpInit
-
- Initialize the module.
-
- Entry: giveTime = pointer to GiveTime function.
-
- The GiveTime function is called during CPU-intensive operations.
- ----------------------------------------------------------------------------*/
-
- void NntpInit (NntpGiveTimeFunction giveTime)
- {
- gGiveTime = giveTime;
- }
-
-
-
- /*----------------------------------------------------------------------------
- NntpIdle
-
- Handle idle time tasks.
-
- Exit: function result = result code (always noErr).
-
- This function checks all the open NNTP streams to see if their idle
- timers have expired. Any streams with expired timers are closed.
- ----------------------------------------------------------------------------*/
-
- OSErr NntpIdle (void)
- {
- TStreamHandle s;
- NetStreamRef netStream;
- short idleTime;
- unsigned long lastTransactionTime, curTime;
-
- GetDateTime(&curTime);
- for (s = gStreamList; s != nil; s = (**s).next) {
- netStream = (**s).netStream;
- idleTime = (**s).options.idleTime;
- if (netStream != nil && idleTime != 0) {
- lastTransactionTime = (**s).lastTransactionTime;
- if (curTime > lastTransactionTime + 60*idleTime) {
- NetClose(netStream);
- (**s).netStream = nil;
- }
- }
- }
- return noErr;
- }
-
-
-
- /*----------------------------------------------------------------------------
- NntpOpen
-
- Open an NNTP stream.
-
- Entry: host = server host address (domain name or dotted
- decimal IP address).
- options = pointer to server options, or nil to set default options.
-
- Exit: function result = result code.
- *stream = reference to opened stream, or nil if not opened.
-
- If the sendModeReader stream option is set, a "MODE READER" command is
- sent to the server after connecting. This command is also sent in the
- future on any automatic reconnects to the server. Some INN servers require
- this command.
- ----------------------------------------------------------------------------*/
-
- OSErr NntpOpen (char *host, NntpStreamOptions *options, NntpStreamRef *stream)
- {
- TStreamHandle s = nil;
- unsigned long addr;
- unsigned short port;
- NetStreamRef netStream;
- CStr255 command, response;
- long responseCode;
- OSErr err = noErr;
-
- *stream = nil;
-
- err = MyNewHandle(sizeof(TStream), &s);
- if (err != noErr) return err;
-
- err = NetNameToAddr(host, kNNTPPort, &addr, &port);
- if (err != noErr) goto exit;
-
- err = NetOpen(addr, port, true, &netStream, &responseCode, response);
- if (err != noErr) goto exit;
-
- (**s).netStream = netStream;
- if (options == nil) {
- (**s).options.idleTime = 0;
- (**s).options.useXPAT = false;
- (**s).options.sendModeReader = true;
- (**s).options.batchedCmds = false;
- (**s).options.newConnection = true;
- (**s).options.authOnConnect = false;
- } else {
- (**s).options = *options;
- }
- (**s).serverHasXPAT = true;
- (**s).addr = addr;
- (**s).port = port;
- *(**s).curGroup = 0;
- (**s).next = gStreamList;
- gStreamList = s;
- *stream = (NntpStreamRef)s;
- strcpy((**s).helloMsg, response);
-
- if (responseCode != 200 && responseCode != 201) return nntpServerErr;
-
- if (options == nil || options->sendModeReader) {
- strcpy(command, "MODE READER");
- err = NetCommand(netStream, command, &responseCode, response);
- if (err != noErr) goto exit;
- }
-
- if (options != nil && options->authOnConnect) {
- err = Authorize(s);
- if (err == nntpAuthFailedErr) return err;
- if (err != noErr) goto exit;
- }
-
- SetLastTransactionTime(s);
-
- return noErr;
-
- exit:
-
- MyDisposeHandle(s);
- *stream = nil;
- return err;
-
- }
-
-
-
- /*----------------------------------------------------------------------------
- NntpClose
-
- Close an NNTP stream.
-
- Entry: stream = stream reference.
-
- Exit: function result = result code (always noErr).
- ----------------------------------------------------------------------------*/
-
- OSErr NntpClose (NntpStreamRef stream)
- {
- TStreamHandle s, prev, cur;
- NetStreamRef netStream;
-
- s = (TStreamHandle)stream;
-
- for (prev = nil, cur = gStreamList;
- cur != nil && cur != s;
- prev = cur, cur = (**cur).next) /* do nothing */ ;
- if (cur != nil) {
- if (prev == nil) {
- gStreamList = (**cur).next;
- } else {
- (**prev).next = (**cur).next;
- }
- }
-
- netStream = (**s).netStream;
- if (netStream != nil) NetClose(netStream);
- MyDisposeHandle(s);
- return noErr;
- }
-
-
-
- /*----------------------------------------------------------------------------
- NntpAbort
-
- Abort an NNTP stream.
-
- Entry: stream = stream reference.
-
- Exit: function result = result code (always noErr).
- ----------------------------------------------------------------------------*/
-
- OSErr NntpAbort (NntpStreamRef stream)
- {
- TStreamHandle s;
- NetStreamRef netStream;
-
- s = (TStreamHandle)stream;
- netStream = (**s).netStream;
- if (netStream == nil) return noErr;
- NetClose(netStream);
- (**s).netStream = nil;
- return noErr;
- }
-
-
-
- /*----------------------------------------------------------------------------
- NntpSetStreamOptions
-
- Set stream options.
-
- Entry: stream = stream reference.
- options = pointer to stream options.
-
- Exit: function result = result code (always noErr).
- ----------------------------------------------------------------------------*/
-
- OSErr NntpSetStreamOptions (NntpStreamRef stream, NntpStreamOptions *options)
- {
- TStreamHandle s;
-
- s = (TStreamHandle)stream;
- (**s).options = *options;
- return noErr;
- }
-
-
-
- /*----------------------------------------------------------------------------
- NntpGetGroupNames
-
- Get the full list of group names or a list of names for new groups
- from the server.
-
- Entry: stream = stream reference.
- time = 0 to get full group list.
- time != 0 to only get new groups created since the specified time.
- statusFilter = a C-format string of characters. Only the names of
- groups with status not equal to one of the characters in this
- string are returned. The string should be in lower case.
-
- Exit: function result = result code.
- strings = handle to group name strings.
- numGroups = number of group names.
-
- The time parameter is in GetDateTime format (seconds since 01/01/04). It
- is interpreted by the server, not by the Mac. To avoid missing new groups
- due to differences between the server's time and the Mac's time, you
- should subtract a healthy amount from the time of your last new groups
- check (e.g., 36 hours), then discard any duplicate groups returned.
-
- The strings block contains the C-format group names. Group names are
- truncated to 127 characters.
- ----------------------------------------------------------------------------*/
-
- OSErr NntpGetGroupNames (NntpStreamRef stream, unsigned long time,
- char *statusFilter, Handle *strings, long *numGroups)
- {
- TStreamHandle s;
- NetStreamRef netStream;
- DateTimeRec timeRec;
- Handle text = nil;
- char *p, *pEnd, *q, *r, status;
- long n, len;
- CStr255 command, response;
- long responseCode;
- OSErr err = noErr;
-
- s = (TStreamHandle)stream;
- err = ReOpenConnection(s);
- if (err != noErr) return err;
- netStream = (**s).netStream;
-
- if (time == 0) {
- strcpy(command, "LIST");
- } else {
- SecondsToDate(time, &timeRec);
- timeRec.year = timeRec.year % 100;
- sprintf(command, "NEWGROUPS %02d%02d%02d %02d%02d%02d",
- timeRec.year, timeRec.month, timeRec.day,
- timeRec.hour, timeRec.minute, timeRec.second);
- }
-
- err = NetCommand(netStream, command, &responseCode, response);
- if (err != noErr) goto exit1;
- if (responseCode != (time == 0 ? 215 : 231)) goto exit2;
-
- err = NetGetText(netStream, &text, nil, nil);
- if (err != noErr) goto exit1;
-
- MyHLock(text);
- p = q = *text;
- pEnd = p + MyGetHandleSize(text);
- n = 0;
- while (p < pEnd) {
- err = (*gGiveTime)();
- if (err != noErr) goto exit3;
- r = p;
- while (*r != ' ' && *r != CR) r++;
- len = r-p;
- if (len > 127) len = 127;
- while (*r == ' ') r++;
- while (*r != ' ' && *r != CR) r++;
- while (*r == ' ') r++;
- while (*r != ' ' && *r != CR) r++;
- while (*r == ' ') r++;
- status = tolower(*r);
- while (*r != CR) r++;
- if (strchr(statusFilter, status) == nil) {
- *(p+len) = 0;
- strcpy(q, p);
- q += len+1;
- n++;
- }
- p = r+1;
- }
- len = q-*text;
- MyHUnlock(text);
-
- MySetHandleSize(text, len);
- *strings = text;
- *numGroups = n;
- SetLastTransactionTime(s);
-
- return noErr;
-
- exit1:
-
- MyDisposeHandle(text);
- (**s).netStream = nil;
- return err;
-
- exit2:
-
- MyDisposeHandle(text);
- return nntpServerErr;
-
- exit3:
-
- MyDisposeHandle(text);
- return err;
- }
-
-
-
- /*----------------------------------------------------------------------------
- NntpGetGroupInfo
-
- Get info for a single group.
-
- Entry: stream = stream reference.
- group = C-format group name.
-
- Exit: function result = result code.
- first = first article number in group.
- last = last article number in group.
- count = server's estimate of number of articles in the group.
-
- This function returns nntpNoSuchGroupErr if the group does not exist.
-
- If the newConnection stream option is set, the current connection to
- the news server is closed and a new one is opened before getting the
- group information. You need to do this with some kinds of news servers
- (e.g., the reference implementation server). This close/reopen is
- not done, however, if the previous call to NntpGetGroupInfo was
- within 10 seconds ago. This prevents rapid sequences of calls to
- NntpGetGroupInfo from closing and reopening.
- ----------------------------------------------------------------------------*/
-
- OSErr NntpGetGroupInfo (NntpStreamRef stream, char *group,
- long *first, long *last, long *count)
- {
- TStreamHandle s;
- NetStreamRef netStream;
- CStr255 command, response;
- long responseCode;
- OSErr err = noErr;
- char *p;
- static long prevTick = 0;
-
- s = (TStreamHandle)stream;
-
- if ((**s).options.newConnection && TickCount() > prevTick + 10*60) {
- netStream = (**s).netStream;
- if (netStream != nil) {
- NetClose(netStream);
- (**s).netStream = nil;
- }
- }
-
- err = ReOpenConnection(s);
- if (err != noErr) return err;
- netStream = (**s).netStream;
-
- *(**s).curGroup = 0;
-
- sprintf(command, "GROUP %.127s", group);
- err = NetCommand(netStream, command, &responseCode, response);
- if (err != noErr) goto exit1;
- if (responseCode == 502) {
- *count = 0;
- *first = 1;
- *last = 0;
- } else if (responseCode == 211) {
- p = response;
- CrackNum(&p);
- *count = CrackNum(&p);
- *first = CrackNum(&p);
- *last = CrackNum(&p);
- } else if (responseCode == 411) {
- return nntpNoSuchGroupErr;
- } else {
- return nntpServerErr;
- }
-
- SetCurrentGroup(s, group);
- SetLastTransactionTime(s);
- prevTick = TickCount();
-
- return noErr;
-
- exit1:
-
- (**s).netStream = nil;
- return err;
- }
-
-
-
- /*----------------------------------------------------------------------------
- NntpGetMultipleGroupInfo
-
- Get info for multiple groups.
-
- Entry: stream = stream reference.
- info = handle to array of group info.
- numGroups = number of groups in group info array.
- strings = handle to group name strings.
-
- Exit: function result = result code.
-
- For each group in the array, the following information is returned in
- the group info array:
-
- first = first article number in group.
- last = last article number in group.
- count = estimate of number of articles in the group.
- ok = true if other info is valid, false if group does not exist
- or some other server error occured.
-
- If the newConnection stream option is set, the current connection to
- the news server is closed and a new one is opened before getting the
- group information. You need to do this with some kinds of news servers
- (e.g., the reference implementation server).
-
- If the batchedCmds stream option is set, multiple GROUP commands are
- sent to the news server in a batch, and the responses are processed as
- they arrive. If the batchedCmds stream option is not set, the GROUP
- commands are sent one at a time. Batched commands are much faster, but
- they do not work with all servers.
- ----------------------------------------------------------------------------*/
-
- OSErr NntpGetMultipleGroupInfo (NntpStreamRef stream, NntpGroupInfoHandle info,
- long numGroups, Handle strings)
- {
- TStreamHandle s;
- NetStreamRef netStream;
- NntpGroupInfoPtr x, xEnd;
- Boolean savedNewConnectionOption;
- long cmdBufSize;
- Handle cmdBuf;
- char *p;
- short len;
- short state1, state2;
- TGetMultipleGroupInfo z;
- OSErr err = noErr;
-
- state1 = MyHGetState(info);
- state2 = MyHGetState(strings);
-
- if (numGroups == 0) return noErr;
-
- s = (TStreamHandle)stream;
-
- if ((**s).options.newConnection) {
- netStream = (**s).netStream;
- if (netStream != nil) {
- NetClose(netStream);
- (**s).netStream = nil;
- }
- }
-
- err = ReOpenConnection(s);
- if (err != noErr) return err;
- netStream = (**s).netStream;
-
- *(**s).curGroup = 0;
-
- if ((**s).options.batchedCmds) {
-
- cmdBufSize = 0;
- xEnd = *info + numGroups;
- for (x = *info; x < xEnd; x++)
- cmdBufSize += 7 + strlen(*strings + x->offset);
- err = MyNewHandle(cmdBufSize, &cmdBuf);
- if (err != noErr) return err;
- p = *cmdBuf;
- xEnd = *info + numGroups;
- for (x = *info; x < xEnd; x++) {
- BlockMoveData("GROUP ", p, 6);
- p += 6;
- len = strlen(*strings + x->offset);
- BlockMoveData(*strings + x->offset, p, len);
- p += len;
- *p++ = CR;
- }
- z.groupInfo = info;
- z.index = 0;
- z.serverError = false;
- err = NetBatchedCommands(netStream, cmdBuf, DoOneGroupCmdResponse, (Ptr)&z);
- MyDisposeHandle(cmdBuf);
- if (err != noErr) goto exit1;
- if (z.serverError) return nntpServerErr;
-
- } else {
-
- savedNewConnectionOption = (**s).options.newConnection;
- (**s).options.newConnection = false;
- MyHLock(info);
- MyHLock(strings);
- xEnd = *info + numGroups;
- for (x = *info; x < xEnd; x++) {
- err = NntpGetGroupInfo(stream, *strings + x->offset,
- &x->first, &x->last, &x->count);
- if (err == noErr) {
- x->ok = true;
- } else if (err == nntpNoSuchGroupErr || err == nntpServerErr) {
- x->ok = false;
- } else {
- goto exit2;
- }
- }
- MyHSetState(info, state1);
- MyHSetState(strings, state2);
- (**s).options.newConnection = savedNewConnectionOption;
-
- }
-
- SetLastTransactionTime(s);
-
- return noErr;
-
- exit1:
-
- MyHSetState(info, state1);
- MyHSetState(strings, state2);
- (**s).netStream = nil;
- return err;
-
- exit2:
-
- MyHSetState(info, state1);
- MyHSetState(strings, state2);
- (**s).options.newConnection = savedNewConnectionOption;
- return err;
-
- }
-
-
-
- /*----------------------------------------------------------------------------
- NntpGetHeaders
-
- Get selected article headers.
-
- Entry: stream = stream reference.
- group = group name.
- first = first article number.
- last = last article number.
- header = header name.
- pattern = search string.
- buildXPAT = pointer to function to build XPAT pattern
- for search string. Ignored if pattern nil or empty.
- matchPattern = pointer to function to compare search string
- to target string. Ignored if pattern nil or empty.
-
- Exit: function result = result code.
- info = handle to array of header info.
- strings = handle to header strings.
- numHeaders = number of headers.
-
- If the pattern parameter is nil or the empty string, all headers in the
- range [first,last] are returned.
-
- If the pattern parameter is not nil or empty, only headers in the range
- [first,last] which match the pattern are returned.
-
- The strings block contains the C-format header strings, one after another.
- The offset fields in the header info array contain offsets into this
- block. Header strings are truncated to 255 characters, and have leading
- and trailing blanks stripped.
-
- All returned elements of the header info array have article numbers in
- the range [first,last]. The header info array is sorted by article number,
- with any duplicates eliminated.
-
- If the useXPAT stream option is set, and if a search pattern is specified,
- the buildXPAT callback function is called to build the regular expression
- XPAT pattern. An XPAT command is then sent to the server to get the
- matching headers.
-
- If the useXPAT stream option is not set, or if the server does not support
- the XPAT command, all the headers are fetched from the server, and the
- matchPattern callback function is called to extract the matching headers.
- ----------------------------------------------------------------------------*/
-
- OSErr NntpGetHeaders (NntpStreamRef stream, char *group, long first, long last,
- char *header, char *pattern,
- void (*buildXPAT)(char *pattern, char *regExp, short regExpLen),
- Boolean (*matchPattern)(char *pattern, char *string),
- NntpHeaderInfoHandle *info, Handle *strings, long *numHeaders)
- {
- TStreamHandle s;
- NetStreamRef netStream;
- CStr255 command, response;
- long responseCode;
- OSErr err = noErr;
- Handle text = nil;
- NntpHeaderInfoHandle theInfo = nil;
- NntpHeaderInfoPtr x;
- char *p, *pEnd, *q, *r, *t;
- long n, len, number, prevNumber;
- char xpatCmd[1000];
- Boolean mustFilter;
- Boolean mustSort = false;
- long numToMove;
-
- s = (TStreamHandle)stream;
- err = ReOpenConnection(s);
- if (err != noErr) return err;
- netStream = (**s).netStream;
-
- if (!MyStrEqual(group, (**s).curGroup)) {
- *(**s).curGroup = 0;
- sprintf(command, "GROUP %.127s", group);
- err = NetCommand(netStream, command, &responseCode, response);
- if (err != noErr) goto exit1;
- if (responseCode == 411) return nntpNoSuchGroupErr;
- if (responseCode != 211) return nntpServerErr;
- SetCurrentGroup(s, group);
- }
-
- while (true) {
- if (pattern == nil || *pattern == 0 || !(**s).options.useXPAT || !(**s).serverHasXPAT) {
- sprintf(command, "XHDR %.127s %ld-%ld", header, first, last);
- err = NetCommand(netStream, command, &responseCode, response);
- if (err != noErr) goto exit1;
- if (responseCode != 221) return nntpServerErr;
- mustFilter = pattern != nil && *pattern != 0;
- break;
- } else {
- sprintf(xpatCmd, "XPAT %.127s %ld-%ld ", header, first, last);
- len = strlen(xpatCmd);
- (*buildXPAT)(pattern, xpatCmd + len, 1000 - len);
- len = strlen(xpatCmd);
- if (len > 255) len = 255;
- BlockMoveData(xpatCmd, command, len);
- *(command + len) = 0;
- err = NetCommand(netStream, xpatCmd, &responseCode, response);
- if (err != noErr) goto exit1;
- if (responseCode == 500) {
- (**s).serverHasXPAT = false;
- continue;
- }
- if (responseCode != 221) return nntpServerErr;
- mustFilter = false;
- break;
- }
- }
-
- err = NetGetText(netStream, &text, nil, nil);
- if (err != noErr) goto exit1;
-
- pEnd = *text + MyGetHandleSize(text);
- n = 0;
- for (p = *text; p < pEnd; p++) if (*p == CR) n++;
-
- err = MyNewHandle(n*sizeof(NntpHeaderInfo), &theInfo);
- if (err != noErr) goto exit3;
-
- MyHLock(text);
- MyHLock(theInfo);
- x = *theInfo;
- p = q = *text;
- pEnd = *text + MyGetHandleSize(text);
- n = 0;
- while (p < pEnd) {
- number = CrackNum(&p);
- if (number < first || number > last) {
- while (*p != CR) p++;
- p++;
- continue;
- }
- while (*p == ' ') p++;
- r = p;
- while (*r != CR) r++;
- t = r-1;
- while (t >= p && *t == ' ') t--;
- len = t-p+1;
- if (len > 255) len = 255;
- *(p+len) = 0;
- if (!mustFilter || (*matchPattern)(pattern, p)) {
- if (n > 0 && number <= prevNumber) mustSort = true;
- prevNumber = number;
- x->number = number;
- x->offset = q - *text;
- strcpy(q, p);
- q += len+1;
- n++;
- x++;
- }
- p = r+1;
- }
- if (mustSort) {
- err = FastQSort(*theInfo, n, sizeof(NntpHeaderInfo),
- (SortCmpFunction)SortHeadersCompare);
- if (err != noErr) goto exit3;
- if (n > 1) {
- x = *theInfo;
- for (numToMove = (n-1)*sizeof(NntpHeaderInfo);
- numToMove > 0;
- numToMove -= sizeof(NntpHeaderInfo))
- {
- if (x->number == (x+1)->number) {
- BlockMoveData(x+1, x, numToMove);
- n--;
- } else {
- x++;
- }
- }
- }
- }
- len = q - *text;
- MyHUnlock(text);
- MyHUnlock(theInfo);
-
- MySetHandleSize(text, len);
- MySetHandleSize(theInfo, n*sizeof(NntpHeaderInfo));
- *info = theInfo;
- *strings = text;
- *numHeaders = n;
- SetLastTransactionTime(s);
-
- return noErr;
-
- exit1:
-
- MyDisposeHandle(text);
- MyDisposeHandle(theInfo);
- (**s).netStream = nil;
- return err;
-
- exit2:
-
- MyDisposeHandle(text);
- MyDisposeHandle(theInfo);
- return nntpServerErr;
-
- exit3:
-
- MyDisposeHandle(text);
- MyDisposeHandle(theInfo);
- return err;
- }
-
-
-
- /*----------------------------------------------------------------------------
- NntpGetArticle
-
- Get an article.
-
- Entry: stream = stream reference.
- group = group name, or nil if fetching by message id.
- number = article number. Ignored if fetching by message id.
- id = message id string, including < and > delimiters. Ignored
- if fetching by article number.
- part = which part of the article to get:
- "ARTICLE": full article text, header and body.
- "HEAD": only article header.
- "BODY": only article body.
- text = pointer to handle in which to return text,
- or nil if none.
- chunkFunction = pointer to chunk processing function,
- or nil if none.
- userDataPtr = pointer to user data to be passed through
- to the chunk processing function, or nil if none.
-
- Exit: function result = error code.
- *text = handle to article text, if text != nil.
-
- The chunk processing function, if any, is called every time a new
- block of article text is received from the server. This function must be
- declared as follows:
-
- OSErr ProcessTextChunk (Ptr t, long tLen, Ptr userDataPtr,
- long *truncPos)
-
- Entry: t = pointer to raw text received from server.
- tLen = length of text received from server.
- userDataPtr = pointer to user data.
-
- Exit: function result = error code.
- *truncPos = position at which to truncate text if
- error code is netTruncatedErr.
-
- If text == nil, the article text is processed by the chunk processing
- function in pieces (e.g., saved to a file as it comes in over the
- network). In this case, the text is not accumulated and returned as a
- whole to the NntpGetArticle caller, and the "t" and "tLen" parameters
- passed to the chunk processing function are for just the chunk received.
-
- If text != nil, the text is accumulated as it is received and returned
- as a whole to the NntpGetArticle caller, and the "t" and "tLen" parameters
- passed to the chunk processing function (if any) are for the entire text
- as accumulated so far.
-
- If the chunk processing function returns with an error code, NntpGetArticle
- aborts the stream and returns to the caller immediately with the text handle
- set to just the text received so far. The error code returned by the
- chunk processing function is returned to the NntpGetArticle caller as
- NntpGetArticle's function result.
-
- The text passed to the chunk processing function is raw. It contains CRLF
- line terminators and doubled leading ".." characters. The terminating "."
- on a line by itself, however, is not passed to the chunk processing function.
- ----------------------------------------------------------------------------*/
-
- OSErr NntpGetArticle (NntpStreamRef stream, char *group, long number,
- char *id, char *part, Handle *text, NetChunkFunction chunkFunction,
- Ptr userDataPtr)
- {
- TStreamHandle s;
- NetStreamRef netStream;
- CStr255 command, response;
- long responseCode;
- OSErr err = noErr;
-
- s = (TStreamHandle)stream;
- err = ReOpenConnection(s);
- if (err != noErr) return err;
- netStream = (**s).netStream;
-
- if (group != nil && !MyStrEqual(group, (**s).curGroup)) {
- *(**s).curGroup = 0;
- sprintf(command, "GROUP %.127s", group);
- err = NetCommand(netStream, command, &responseCode, response);
- if (err != noErr) goto exit;
- if (responseCode == 411) return nntpNoSuchGroupErr;
- if (responseCode != 211) return nntpServerErr;
- SetCurrentGroup(s, group);
- }
-
- if (group != nil) {
- sprintf(command, "%s %ld", part, number);
- } else {
- sprintf(command, "%s %.127s", part, id);
- }
- err = NetCommand(netStream, command, &responseCode, response);
- if (err != noErr) goto exit;
- if (responseCode == 423 || responseCode == 430) return nntpNoSuchArticleErr;
- if (responseCode != 220 && responseCode != 221 &&
- responseCode != 222) return nntpServerErr;
-
- err = NetGetText(netStream, text, chunkFunction, userDataPtr);
- if (err != noErr) goto exit;
-
- SetLastTransactionTime(s);
-
- return noErr;
-
- exit:
-
- (**s).netStream = nil;
- return err;
- }
-
-
-
- /*----------------------------------------------------------------------------
- NntpPostArticle
-
- Post an article.
-
- Entry: stream = stream reference.
- text = handle to article text, including header lines,
- with CR line terminators.
- Warning: the memory block is modified by the function.
- The memory block must be unlocked and nonpurgeable.
-
- Exit: function result = result code.
- postIndeterminate = true if entire article sent, but error occured
- or user canceled before final server response received. The
- article may or may not have been posted successfully.
- ----------------------------------------------------------------------------*/
-
- OSErr NntpPostArticle (NntpStreamRef stream, Handle text, Boolean *postIndeterminate)
- {
- TStreamHandle s;
- NetStreamRef netStream;
- CStr255 command, response;
- long responseCode;
- OSErr err = noErr;
-
- *postIndeterminate = false;
-
- s = (TStreamHandle)stream;
- err = ReOpenConnection(s);
- if (err != noErr) return err;
- netStream = (**s).netStream;
-
- strcpy(command, "POST");
- err = NetCommand(netStream, command, &responseCode, response);
- if (err != noErr) goto exit1;
- if (responseCode != 340) return nntpServerErr;
-
- err = NetPutText(netStream, text);
- if (err != noErr) goto exit1;
-
- *postIndeterminate = true;
-
- err = NetGetExtraResponse(netStream, &responseCode, response);
- if (err != noErr) goto exit1;
- *postIndeterminate = false;
- if (responseCode != 240) return nntpServerErr;
-
- SetLastTransactionTime(s);
-
- return noErr;
-
- exit1:
-
- (**s).netStream = nil;
- return err;
- }
-
-
-
- /*----------------------------------------------------------------------------
- NntpAuthorize
-
- Authorize user.
-
- Entry: stream = stream reference.
-
- Exit: function result = result code.
- ----------------------------------------------------------------------------*/
-
- OSErr NntpAuthorize (NntpStreamRef stream)
- {
- TStreamHandle s;
- OSErr err = noErr;
-
- s = (TStreamHandle)stream;
- err = ReOpenConnection(s);
- if (err != noErr) return err;
-
- err = Authorize(s);
- if (err != noErr) return err;
-
- SetLastTransactionTime(s);
-
- return noErr;
- }
-
-
-
- /*----------------------------------------------------------------------------
- NntpGetHello
-
- Get the server hello message.
-
- Entry: stream = stream reference.
-
- Exit: function result = result code (always noErr).
- msg = hello message.
- ----------------------------------------------------------------------------*/
-
- OSErr NntpGetHello (NntpStreamRef stream, CStr255 msg)
- {
- TStreamHandle s;
-
- s = (TStreamHandle)stream;
- strcpy(msg, (**s).helloMsg);
- return noErr;
- }
-
-
-
- /*----------------------------------------------------------------------------
- NntpGetHelp
-
- Get the server help text.
-
- Entry: stream = stream reference.
-
- Exit: function result = result code.
- text = handle to help text, with CR-terminated lines.
- ----------------------------------------------------------------------------*/
-
- OSErr NntpGetHelp (NntpStreamRef stream, Handle *text)
- {
- TStreamHandle s;
- NetStreamRef netStream;
- CStr255 command, response;
- long responseCode;
- OSErr err = noErr;
-
- s = (TStreamHandle)stream;
- err = ReOpenConnection(s);
- if (err != noErr) return err;
- netStream = (**s).netStream;
-
- strcpy(command, "HELP");
- err = NetCommand(netStream, command, &responseCode, response);
- if (err != noErr) goto exit1;
- if (responseCode != 100) return nntpServerErr;
-
- err = NetGetText(netStream, text, nil, nil);
- if (err != noErr) goto exit1;
-
- SetLastTransactionTime(s);
-
- return noErr;
-
- exit1:
-
- (**s).netStream = nil;
- return err;
- }
-
-
-
- /*----------------------------------------------------------------------------
- NntpGetIPAddr
-
- Get server IP address.
-
- Entry: stream = stream reference.
-
- Exit: function result = result code (always noErr).
- ipAddr = IP address of news server.
- ----------------------------------------------------------------------------*/
-
- OSErr NntpGetIPAddr (NntpStreamRef stream, unsigned long *ipAddr)
- {
- TStreamHandle s;
-
- s = (TStreamHandle)stream;
- *ipAddr = (**s).addr;
- return noErr;
- }
-
-
-
- /*----------------------------------------------------------------------------
- NntpGetServerErrInfo
-
- Get server error information.
-
- Entry: stream = stream reference.
-
- Exit: *serverErrInfo = server error information.
- ----------------------------------------------------------------------------*/
-
- void NntpGetServerErrInfo (NntpStreamRef stream, NetServerErrInfo *serverErrInfo)
- {
- TStreamHandle s;
- NetStreamRef netStream;
-
- s = (TStreamHandle)stream;
- netStream = (**s).netStream;
- if (netStream == nil) {
- *(serverErrInfo->command) = 0;
- *(serverErrInfo->response) = 0;
- serverErrInfo->responseCode = 0;
- } else {
- NetGetServerErrInfo(netStream, serverErrInfo);
- if (serverErrInfo->responseCode == 400) {
- NetClose(netStream);
- (**s).netStream = nil;
- }
- }
- }
-